// Copyright 2009 Pigasus.
// GNU GPL v2.
// Author: John Zhang and Jordan Rupprecht.

#include "pigasus-types.h"

#include <iostream>
#include <map>
#include <string>
#include <vector>

using namespace std;

typedef unsigned int uint; // Why is this necessary?

void DeleteElement(string type, void* ptr) {
  if (ptr == NULL) {
    // Trying to delete NULL pointer. Ignore.
    return;
  }

  if (type == kIntType) {
    delete (int*)ptr;
  } else if (type == kFloatType) {
    delete (double*)ptr;
  } else if (type == kStringType) {
    delete (string*)ptr;
  } else if (type == kListType) {
    delete (List*)ptr;
  } else if (type == kMapType) {
    delete (Map*)ptr;
  } else if (type == kFileType) {
    delete (File*)ptr;
  } else {
    // TODO: unknown type inserted; cannot be deleted.
  }
}

List NewList(const int &v) {
  List new_list;
  new_list.Append(v);
  return new_list;
}

List NewList(const double &v) {
  List new_list;
  new_list.Append(v);
  return new_list;
}

List NewList(const string &v) {
  List new_list;
  new_list.Append(v);
  return new_list;
}

List NewList(const List &v) {
  List new_list;
  new_list.Append(v);
  return new_list;
}

List NewList(const Map &v) {
  List new_list;
  new_list.Append(v);
  return new_list;
}

List NewList(const File &v) {
  List new_list;
  new_list.Append(v);
  return new_list;
}

void List::CopyIntoThis(const List &v)  {
  // Empty the current list first.
  for (unsigned int i = 0; i < value.size(); i++) {
    DeleteElement(value[i].first, value[i].second);
  }
  value.clear();
  
  
  // Set this to the specified list.
  List *w = (List*)(&v);
  for (int i = 0; i < w->GetSize(); i++) {
    if (w->GetType(i) == kIntType) {
      Append(*((int*)w->Get(i)));
    } else if (w->GetType(i) == kFloatType) {
      Append(*((double*)w->Get(i)));
    } else if (w->GetType(i) == kStringType) {
      Append(*((string*)w->Get(i)));
    } else if (w->GetType(i) == kListType) {
      Append(*((List*)w->Get(i)));
    } else if (w->GetType(i) == kMapType) {
      Append(*((Map*)w->Get(i)));
    } else if (w->GetType(i) == kFileType) {
      Append(*((File*)w->Get(i)));
    } else {
      // TODO: Unrecognized type found. Ignore for now. Decide what to do.
    }
  }
}

List::List() {
  // Empty constructor.
}

// Overload copy constructor.
List::List(const List &v)  {
  CopyIntoThis(v);
}

// Overload copy operator.
List& List::operator=(const List &v) {
  CopyIntoThis(v);
  return *this;
}

List::~List() {
  for (uint i = 0; i < value.size(); i++) {
    DeleteElement(value[i].first, value[i].second);
  }
}

List* List::Clone() {
  List* copy = new List();

  for (uint i = 0; i < value.size(); i++) {
    if (value[i].first == kIntType) {
      copy->Append(*((int*)value[i].second));
    } else if (value[i].first == kFloatType) {
      copy->Append(*((double*)value[i].second));
    } else if (value[i].first == kStringType) {
      copy->Append(*((string*)value[i].second));
    } else if (value[i].first == kListType) {
      copy->Append(*(((List*)value[i].second)->Clone()));
    } else if (value[i].first == kMapType) {
      copy->Append(*(((Map*)value[i].second)->Clone()));
    } else if (value[i].first == kFileType) {
      copy->Append(*(((File*)value[i].second)->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }

  return copy;  
}

List List::CloneValue() {
  List copy;

  for (uint i = 0; i < value.size(); i++) {
    if (value[i].first == kIntType) {
      copy.Append(*((int*)value[i].second));
    } else if (value[i].first == kFloatType) {
      copy.Append(*((double*)value[i].second));
    } else if (value[i].first == kStringType) {
      copy.Append(*((string*)value[i].second));
    } else if (value[i].first == kListType) {
      copy.Append(*(((List*)value[i].second)->Clone()));
    } else if (value[i].first == kMapType) {
      copy.Append(*(((Map*)value[i].second)->Clone()));
    } else if (value[i].first == kFileType) {
      copy.Append(*(((File*)value[i].second)->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }

  return copy;  
}

void* List::Get(int index) {
  if (index >= static_cast<int>(value.size())) {
    return NULL;
  } else {
    return value[index].second;
  }
}

void* List::operator[](const int &index) {
  return Get(index);
}

void List::Append(const int &v) {
  int *t = new int(v);
  value.push_back(pair<string, void*>(kIntType, (void*)t));
}

void List::Append(const double &v) {
  double *t = new double(v);
  value.push_back(pair<string, void*>(kFloatType, (void*)t));
}

void List::Append(const string &v) {
  string *t = new string(v);
  value.push_back(pair<string, void*>(kStringType, (void*)t));
}

void List::Append(const List &v) {
  List *t = ((List*)(&v))->Clone();
  value.push_back(pair<string, void*>(kListType, (void*)t));
}

void List::Append(const Map &v) {
  Map *t = ((Map*)(&v))->Clone();
  value.push_back(pair<string, void*>(kMapType, (void*)t));
}

void List::Append(const File &v) {
  File *t = ((File*)(&v))->Clone();
  value.push_back(pair<string, void*>(kFileType, (void*)t));
}

void List::Set(int index, const int &v) {
  if (index >= GetSize()) {
    // TODO: decide how to handle index-out-of-bounds error.
    return;
  }

  DeleteElement(GetType(index), Get(index));
  int *t = new int(v);
  value[index] = pair<string, void*>(kIntType, (void*)t);
}

void List::Set(int index, const double &v) {
  if (index >= GetSize()) {
    // TODO: decide how to handle index-out-of-bounds error.
    return;
  }

  DeleteElement(GetType(index), Get(index));
  double *t = new double(v);
  value[index] = pair<string, void*>(kFloatType, (void*)t);
}

void List::Set(int index, const string &v) {
  if (index >= GetSize()) {
    // TODO: decide how to handle index-out-of-bounds error.
    return;
  }

  DeleteElement(GetType(index), Get(index));
  string *t = new string(v);
  value[index] = pair<string, void*>(kStringType, (void*)t);
}

void List::Set(int index, const List &v) {
  if (index >= GetSize()) {
    // TODO: decide how to handle index-out-of-bounds error.
    return;
  }

  DeleteElement(GetType(index), Get(index));
  List *t = ((List*)(&v))->Clone();
  value[index] = pair<string, void*>(kListType, (void*)t);
}

void List::Set(int index, const Map &v) {
  if (index >= GetSize()) {
    // TODO: decide how to handle index-out-of-bounds error.
    return;
  }

  DeleteElement(GetType(index), Get(index));
  Map *t = ((Map*)(&v))->Clone();
  value[index] = pair<string, void*>(kMapType, (void*)t);
}

void List::Set(int index, const File &v) {
  if (index >= GetSize()) {
    // TODO: decide how to handle index-out-of-bounds error.
    return;
  }

  DeleteElement(GetType(index), Get(index));
  File *t = ((File*)(&v))->Clone();
  value[index] = pair<string, void*>(kFileType, (void*)t);
}

List List::Concatenate(const List &v) {
  List t;

  // Add items from current List.
  for (int i = 0; i < GetSize(); i++) {
    if (GetType(i) == kIntType) {
      t.Append(*((int*)Get(i)));
    } else if (GetType(i) == kFloatType) {
      t.Append(*((double*)Get(i)));
    } else if (GetType(i) == kStringType) {
      t.Append(*((string*)Get(i)));
    } else if (GetType(i) == kListType) {
      t.Append(*((List*)Get(i)));
    } else if (GetType(i) == kMapType) {
      t.Append(*((Map*)Get(i)));
    } else if (GetType(i) == kFileType) {
      t.Append(*((File*)Get(i)));
    } else {
      // TODO: Unrecognized type found. Ignore for now. Decide what to do.
    }
  }

  // Add items from other List;
  List *w = (List*)(&v);
  for (int i = 0; i < w->GetSize(); i++) {
    if (w->GetType(i) == kIntType) {
      t.Append(*((int*)w->Get(i)));
    } else if (w->GetType(i) == kFloatType) {
      t.Append(*((double*)w->Get(i)));
    } else if (w->GetType(i) == kStringType) {
      t.Append(*((string*)w->Get(i)));
    } else if (w->GetType(i) == kListType) {
      t.Append(*((List*)w->Get(i)));
    } else if (w->GetType(i) == kMapType) {
      t.Append(*((Map*)w->Get(i)));
    } else if (w->GetType(i) == kFileType) {
      t.Append(*((File*)w->Get(i)));
    } else {
      // TODO: Unrecognized type found. Ignore for now. Decide what to do.
    }
  }

  return t;
}

List List::operator+(const List &v) {
  return Concatenate(v);
}

int List::GetSize() {
  return static_cast<int>(value.size());
}

string List::GetType(int index) {
  if (index >= static_cast<int>(value.size())) {
    return kNullType;
  } else {
    return value[index].first;
  }
}

void Map::CopyIntoThis(const Map &v) {
  // Empty out this one first.
  for (map<string, pair<string, void*> >::iterator it = value.begin();
       it != value.end();
       it++) {
    DeleteElement(it->second.first, it->second.second);
  }
  value.clear();

  // Copy the specified map into this one.
  Map *w = (Map*)(&v);
  List keys = w->GetKeys();

  for (int i = 0; i < keys.GetSize(); i++) {
    string key = *((string*)(keys[i]));
    string type = w->GetType(key);

    if (type == kIntType) {
      Set(key, *((int*)(w->Get(key))));
    } else if (type == kFloatType) {
      Set(key, *((double*)(w->Get(key))));
    } else if (type == kStringType) {
      Set(key, *((string*)(w->Get(key))));
    } else if (type == kListType) {
      Set(key, *(((List*)(w->Get(key)))->Clone()));
    } else if (type == kMapType) {
      Set(key, *(((Map*)(w->Get(key)))->Clone()));
    } else if (type == kFileType) {
      Set(key, *(((File*)(w->Get(key)))->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }
}

Map::Map() {
  // Empty constructor.
}

Map::Map(const Map& v) {
  CopyIntoThis(v);
}

Map& Map::operator=(const Map& v) {
  CopyIntoThis(v);
  return *this;
}

Map::Map(const string &key, const int &v) {
  Set(key, v);
}

Map::Map(const string &key, const double &v) {
  Set(key, v);
}

Map::Map(const string &key, const string &v) {
  Set(key, v);
}

Map::Map(const string &key, const List &v) {
  Set(key, v);
}

Map::Map(const string &key, const Map &v) {
  Set(key, v);
}

Map::Map(const string &key, const File &v) {
  Set(key, v);
}

Map::~Map() {
  for (map< string, pair<string, void*> >::iterator it = value.begin();
       it != value.end();
       it++) {
    DeleteElement(it->second.first, it->second.second);
  }
}

Map* Map::Clone() {
  Map *copy = new Map();

  for (map< string, pair<string, void*> >::iterator it = value.begin();
       it != value.end();
       it++) {
    if (it->second.first == kIntType) {
      copy->Set(it->first, *((int*)it->second.second));
    } else if (it->second.first == kFloatType) {
      copy->Set(it->first, *((double*)it->second.second));
    } else if (it->second.first == kStringType) {
      copy->Set(it->first, *((string*)it->second.second));
    } else if (it->second.first == kListType) {
      copy->Set(it->first, *(((List*)it->second.second)->Clone()));
    } else if (it->second.first == kMapType) {
      copy->Set(it->first, *(((Map*)it->second.second)->Clone()));
    } else if (it->second.first == kFileType) {
      copy->Set(it->first, *(((File*)it->second.second)->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }

  return copy;
}

Map Map::CloneValue() {
  Map copy;

  for (map< string, pair<string, void*> >::iterator it = value.begin();
       it != value.end();
       it++) {
    if (it->second.first == kIntType) {
      copy.Set(it->first, *((int*)it->second.second));
    } else if (it->second.first == kFloatType) {
      copy.Set(it->first, *((double*)it->second.second));
    } else if (it->second.first == kStringType) {
      copy.Set(it->first, *((string*)it->second.second));
    } else if (it->second.first == kListType) {
      copy.Set(it->first, *(((List*)it->second.second)->Clone()));
    } else if (it->second.first == kMapType) {
      copy.Set(it->first, *(((Map*)it->second.second)->Clone()));
    } else if (it->second.first == kFileType) {
      copy.Set(it->first, *(((File*)it->second.second)->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }

  return copy;
}

void* Map::Get(string key) {
  if (value.find(key) == value.end()) {
    return NULL;
  } else {
    return value[key].second;
  }
}

void* Map::operator[](const string &key) {
  return Get(key);
}

void Map::Set(const string &key, const int &v) {
  DeleteElement(GetType(key), Get(key));

  int *t = new int(v);
  value[key] = pair<string, void*>(kIntType, (void*)t);
}

void Map::Set(const string &key, const double &v) {
  DeleteElement(GetType(key), Get(key));

  double *t = new double(v);
  value[key] = pair<string, void*>(kFloatType, (void*)t);
}

void Map::Set(const string &key, const string &v) {
  DeleteElement(GetType(key), Get(key));

  string *t = new string(v);
  value[key] = pair<string, void*>(kStringType, (void*)t);
}

void Map::Set(const string &key, const List &v) {
  DeleteElement(GetType(key), Get(key));

  List *t = ((List*)(&v))->Clone();
  value[key] = pair<string, void*>(kListType, (void*)t);
}

void Map::Set(const string &key, const Map &v) {
  DeleteElement(GetType(key), Get(key));

  Map *t = ((Map*)(&v))->Clone();
  value[key] = pair<string, void*>(kMapType, (void*)t);
}

void Map::Set(const string &key, const File &v) {
  DeleteElement(GetType(key), Get(key));

  File *t = ((File*)(&v))->Clone();
  value[key] = pair<string, void*>(kFileType, (void*)t);
}

Map Map::Concatenate(const Map &v) {
  Map t;

  // Add items from current Map.
  for (map< string, pair<string, void*> >::iterator it = value.begin();
       it != value.end();
       it++) {
    if (it->second.first == kIntType) {
      t.Set(it->first, *((int*)it->second.second));
    } else if (it->second.first == kFloatType) {
      t.Set(it->first, *((double*)it->second.second));
    } else if (it->second.first == kStringType) {
      t.Set(it->first, *((string*)it->second.second));
    } else if (it->second.first == kListType) {
      t.Set(it->first, *(((List*)it->second.second)->Clone()));
    } else if (it->second.first == kMapType) {
      t.Set(it->first, *(((Map*)it->second.second)->Clone()));
    } else if (it->second.first == kFileType) {
      t.Set(it->first, *(((File*)it->second.second)->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }

  // Add items from other Map.
  Map *w = (Map*)(&v);
  List keys = w->GetKeys();

  for (int i = 0; i < keys.GetSize(); i++) {
    string key = *((string*)(keys[i]));
    string type = w->GetType(key);

    if (type == kIntType) {
      t.Set(key, *((int*)(w->Get(key))));
    } else if (type == kFloatType) {
      t.Set(key, *((double*)(w->Get(key))));
    } else if (type == kStringType) {
      t.Set(key, *((string*)(w->Get(key))));
    } else if (type == kListType) {
      t.Set(key, *(((List*)(w->Get(key)))->Clone()));
    } else if (type == kMapType) {
      t.Set(key, *(((Map*)(w->Get(key)))->Clone()));
    } else if (type == kFileType) {
      t.Set(key, *(((File*)(w->Get(key)))->Clone()));
    } else {
      // TODO: unknown type found; cannot be copied.
    }
  }

  return t;
}

Map Map::operator+(const Map &v) {
  return Concatenate(v);
}

int Map::GetSize() {
  return static_cast<int>(value.size());
}

string Map::GetType(const string &key) {
  if (value.find(key) == value.end()) {
    return kNullType;
  } else {
    return value[key].first;
  }
}

List Map::GetKeys() {
  List t;

  for (map<string, pair<string, void*> >::iterator it = value.begin();
       it != value.end();
       it++) {
    t.Append(it->first);
  }

  return t;
}

File::File() {
  // Empty constructor.
}

File::File(string filename_) {
  filename = filename_;
}

File::~File() {
  // Empty destructor.
}

File* File::Clone() {
  File *copy = new File(filename);
  return copy;
}

File File::CloneValue() {
  File copy(filename);
  return copy;
}

string File::Filename() {
  return filename;
}



/*
void Test(List a) {
  for (int i = 0; i < a.GetSize(); i++) {
    printf("%s\n", ((string*)a[i])->c_str());
  }
}

void Test(Map a) {
  List k = a.GetKeys();
  for (int i = 0; i < k.GetSize(); i++) {
    string key = *((string*)(k[i]));
    printf("%s -> %s\n", key.c_str(), ((string*)a[key])->c_str());
  }
}

void Test(File a) {
  printf("%s\n", a.Filename().c_str());
}

int main() {
  List aa = NewList("bob") + NewList("bill");
  Test(aa);

  Map bb = Map("a", "b") + Map("c", "d") + Map("e", "f");
  Test(bb);

  File cc = File("a.out");
  Test(cc);

  return 0;
}
*/
